#include "Precompiled.h"
#include "ComponentCollisionHavok.h"
#include "Collision.h"
#include "Config/ConfigHelper.h"
#include "File/VirtualFileSystem.h"
#include "Plugin/ModuleHelper.h"
#include "IComponentEntity.h"
#include <wx/confbase.h>
#include <wx/statline.h>
#include <wx/thread.h>
#include <wx/filename.h>
#include <wx/regex.h>
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>

//#include <Common/Visualize/hkVisualDebugger.h>
#include <Common/Base/hkBase.h>
#include <Common/Base/Reflection/hkClass.h>
#include <Common/Serialize/Resource/hkResource.h>
#include <Common/Serialize/Util/hkSerializeUtil.h>
#include <Common/Serialize/Util/hkRootLevelContainer.h>
#include <Physics/Utilities/Serialize/hkpPhysicsData.h>
#include <Physics/Dynamics/Entity/hkpRigidBody.h>


namespace
{
    const std::string kExt(".hkx");

    template <typename T, typename D>
    inline D *p_cast(const char *pName)
    {
        T *pv = eg::registrate::p_cast<T>(pName);
        return dynamic_cast<D *>(pv);
    }


} // namespace

namespace eg {

EG_MODULE_REGISTER_COMPONENT(ComponentCollisionHavok);


ComponentCollisionHavok::ComponentCollisionHavok(component::ObjectId oId)
: ComponentHelper(oId, collision::kGroup, ComponentHelper::ccAttached)
, mShapeNames(get_prop<File_Tag, std::vector<std::string> >(mProperty, collision::kPropShapeName, std::vector<std::string>(), "{ext = *.hkx; type = hkx; multi = true}"))
{
}


ComponentCollisionHavok::~ComponentCollisionHavok()
{
}


void ComponentCollisionHavok::DeleteThis()
{
    delete this;
}


void ComponentCollisionHavok::Shutdown()
{
    const component::InstanceId kEntityId(OI().Id, Hash("Entity"));
    const component::InstanceId kModelId(OI().Id, Hash("Model"));

    // we are getting destroyed
    if (IComponent *pEntity = component::get(kEntityId))
    {
        IPropertySet& propSet = pEntity->GetPropertyData();
        propSet.RemoveNotifySlot("Collision");
    }
    if (IComponent *pModel = component::get(kModelId))
    {
        IPropertySet& propSet = pModel->GetPropertyData();
        propSet.RemoveNotifySlot("Collision");
    }
    mCollisionAsset.reset();
}


void ComponentCollisionHavok::onEntityChanged(const IProperty *pProperty)
{
    if (mCollisionAsset)
    {
        IComponentEntity *pEntity = component::cast<IComponentEntity>(this);
        wxASSERT(pEntity);
        Matrix44 bodyTransform = pEntity->GetMatrix();
        mCollisionAsset->SetTransform(bodyTransform);
    }
}


void ComponentCollisionHavok::onModelChanged(const IProperty *pProperty)
{
    if (pProperty->GetName() == "GeometryName")
    {
        std::vector<std::string> meshNames = (*pProperty);
        // we need to strip any extension from it
        VirtualFileSystem& vfs = registrate::ref_cast<VirtualFileSystem>("VirtualFileSystem");

        std::vector<std::string> shapeNames;
        for (std::vector<std::string>::const_iterator it = meshNames.begin(); it != meshNames.end(); ++it)
        {
            shapeNames.push_back(vfs.GetCleanName(*it));
        }

        // this will trigger property change event
        mShapeNames = shapeNames;
    }
}


Dispatcher::Result_t ComponentCollisionHavok::HandleMessage(Message& msg)
{
    const component::InstanceId kEntityId(OI().Id, Hash("Entity"));
    const component::InstanceId kModelId(OI().Id, Hash("Model"));

    switch (msg.mType)
    {
    case mtype::kComponentNew:
        if (msg.Is<component::InstanceId>())
        {
            component::InstanceId sourceId = msg.GetValue<component::InstanceId>();
            if (sourceId == component::getId(this))
            {
                // this message is generated by this object creation,
                // so treat this as a initial setup (run once)
                if (IComponent *pEntity = component::get(kEntityId))
                {
                    IPropertySet& propSet = pEntity->GetPropertyData();
                    propSet.AddNotifySlot(boost::bind(&ComponentCollisionHavok::onEntityChanged, this, _1), "Collision");
                    // any trigger will do
                    onEntityChanged(propSet.FindProperty("Position"));
                }
                if (IComponent *pModel = component::get(kModelId))
                {
                    IPropertySet& propSet = pModel->GetPropertyData();
                    propSet.AddNotifySlot(boost::bind(&ComponentCollisionHavok::onModelChanged, this, _1), "Collision");
                    onModelChanged(propSet.FindProperty("GeometryName"));
                }
            }
            else if (sourceId.first == OI().Id)
            {
                // one of our components is getting created
                if (sourceId == kEntityId)
                {
                    if (IComponent *pEntity = component::get(sourceId))
                    {
                        IPropertySet& propSet = pEntity->GetPropertyData();
                        propSet.AddNotifySlot(boost::bind(&ComponentCollisionHavok::onEntityChanged, this, _1), "Collision");
                        onEntityChanged(propSet.FindProperty("Position"));
                    }
                }
                else if (sourceId == kModelId)
                {
                    if (IComponent *pModel = component::get(sourceId))
                    {
                        IPropertySet& propSet = pModel->GetPropertyData();
                        propSet.AddNotifySlot(boost::bind(&ComponentCollisionHavok::onModelChanged, this, _1), "Collision");
                        onModelChanged(propSet.FindProperty("GeometryName"));
                    }
                }
            }
        }
        break;
    }
}


void ComponentCollisionHavok::onPropertyChanged(const IProperty *pProperty)
{
    if (pProperty->GetPropId() == mShapeNames.GetPropId())
    {
        // \noto for now we just load havok file here
        // but later we'll move it to Asset type object
        //std::string pathName = mShapeName;
        std::vector<std::string> assetList = mShapeNames;
        //assetList.push_back(pathName);
        // let's assure that we have correct extension supplied
        for (std::vector<std::string>::iterator it = assetList.begin(); it != assetList.end(); ++it)
        {
            if (!boost::iends_with(*it, kExt))
            {
                (*it) += kExt;
            }
        }

        Matrix44 bodyTransform(m44::Identity());
        if (IComponentEntity *pEntity = component::cast<IComponentEntity>(this))
        {
            bodyTransform = pEntity->GetMatrix();
        }
        mCollisionAsset.reset(new CollisionAsset(OI().Id, assetList, bodyTransform));
    }
}


Hash ComponentCollisionHavok::GetComponentTypeId() const
{
    return ComponentType::kCollision;
}


bool ComponentCollisionHavok::RegisterComponentType()
{
    InitComponentType(ComponentType::kCollision, wxT("Collision"));

    IObjectManager& om = registrate::ref_cast<IObjectManager>("IObjectManager");
    om.SubscribeToMessageType(ComponentType::kCollision, mtype::kComponentNew);
    return true;
}


ComponentCollisionHavok::CollisionAsset::CollisionAsset(uint64_t oId, const std::vector<std::string>& assetNames, const Matrix44& transform)
: mTransform(transform)
, moId(oId)
{
    if (Collision *collision = p_cast<ICollision, Collision>("Collision"))
    {
        VirtualFileSystem& vfs = registrate::ref_cast<VirtualFileSystem>("VirtualFileSystem");

        for (std::vector<std::string>::const_iterator it = assetNames.begin(); it != assetNames.end(); ++it)
        {
            const std::string& assetName = *it;
            std::string pathName = vfs.GetFqn(assetName);

            hkResource *pShape = hkSerializeUtil::load(pathName.c_str());
            if (hkRootLevelContainer *data = pShape->getContents<hkRootLevelContainer>())
            {
                if (hkpPhysicsData *physData = (hkpPhysicsData *)data->findObjectByType("hkpPhysicsData"))
                {
                    for (int i = 0; i < physData->getPhysicsSystems().getSize(); ++i)
                    {
                        // this has clone() method which will share most static
                        // data but position, etc are unique per clone
                        hkpPhysicsSystem *physicsSystem = physData->getPhysicsSystems()[i];
                        collision->AddToSimulation(physicsSystem, moId);
                    }

                    mShapes.push_back(pShape);
                    pShape = 0;
                }
            }

            if (pShape)
            {
                pShape->removeReference();
                pShape = 0;
            }
        }

        SetTransform(transform);
    }
}


ComponentCollisionHavok::CollisionAsset::~CollisionAsset()
{
    if (Collision *collision = p_cast<ICollision, Collision>("Collision"))
    {
        for (Shapes_t::iterator it = mShapes.begin(); it != mShapes.end(); ++it)
        {
            hkResource *pShape = *it;
            // we don't do any validations for pointers, since if 
            // mShape is valid, then all data will be valid
            hkRootLevelContainer *data = pShape->getContents<hkRootLevelContainer>();
            wxASSERT(data);
            hkpPhysicsData *physData = (hkpPhysicsData *)data->findObjectByType("hkpPhysicsData");
            wxASSERT(physData);
            for (int i = 0; i < physData->getPhysicsSystems().getSize(); ++i)
            {
                hkpPhysicsSystem *physicsSystem = physData->getPhysicsSystems()[i];
                collision->RemoveFromSimulation(physicsSystem, moId);
            }
            pShape->removeReference();
            pShape = 0;
        }
    }
}


void ComponentCollisionHavok::CollisionAsset::SetTransform(const Matrix44& transform)
{
    mTransform = transform;
    hkTransform bodyTransform;
    bodyTransform.set4x4ColumnMajor(mTransform.GetF());

    for (Shapes_t::iterator it = mShapes.begin(); it != mShapes.end(); ++it)
    {
        hkResource *pShape = *it;
        hkRootLevelContainer *data = pShape->getContents<hkRootLevelContainer>();
        wxASSERT(data);
        hkpPhysicsData *physData = (hkpPhysicsData *)data->findObjectByType("hkpPhysicsData");
        wxASSERT(physData);
        for (int i = 0; i < physData->getPhysicsSystems().getSize(); ++i)
        {
            hkpPhysicsSystem *physicsSystem = physData->getPhysicsSystems()[i];
            const hkArray<hkpRigidBody*>& rigidBodies = physicsSystem->getRigidBodies();
            for (int i = 0; i < rigidBodies.getSize(); i++)
            {
                hkpRigidBody *pBody = rigidBodies[i];
                pBody->setTransform(bodyTransform);
            }
        }
    }
}

} // namespace eg
